/* eslint-disable no-redeclare */
const noop = () => { };
const isObject = (obj) => {
  return Object.prototype.toString.call(obj) === '[object Object]';
};
const isArray = (arr) => {
  return Object.prototype.toString.call(arr) === '[object Array]';
};
const isFunction = (arr) => {
  return Object.prototype.toString.call(arr) === '[object Function]';
};
const isString = (str) => {
  return Object.prototype.toString.call(str) === '[object String]';
};
const isBoolean = (str) => {
  return Object.prototype.toString.call(str) === '[object Boolean]';
};
const isUndefined = (str) => {
  return Object.prototype.toString.call(str) === '[object Undefined]';
};
const isNull = (str) => {
  return Object.prototype.toString.call(str) === '[object Null]';
};
const isNumber = (str) => {
  return Object.prototype.toString.call(str) === '[object Number]';
};
export function isDate(obj) {
  return Object.prototype.toString.call(obj) === '[object Date]';
}

const stringify = (obj) => {
  return JSON.stringify(obj);
};
const parse = (str) => {
  return JSON.parse(str);
};

/**
 * options.isReverse  是否反向循环
 * */
const forEach = (obj, callback, options) => {
  options = options || {};
  callback = callback || noop;
  const { isReverse } = options;
  const loopObj = () => {
    for (const key in obj) {
      callback(obj[key], key, obj);
    }
  };
  const loopArr = () => {
    if (isReverse) {
      for (let i = obj.length - 1; i >= 0; i--) {
        callback(obj[i], i);
      }
    } else {
      for (let j = 0, len = obj.length; j < len; j++) {
        callback(obj[j], j);
      }
    }
  };
  if (isObject(obj)) {
    loopObj();
  }
  if (isArray(obj)) {
    loopArr();
  }
};

const isEmpty = (obj) => {
  let result = true;
  if (isObject(obj)) {
    forEach(obj, () => {
      result = false;
    });
  }
  if (isString(obj) || isArray(obj)) {
    result = obj.length === 0;
  }
  if (isNumber(obj)) {
    result = obj === 0;
  }
  return result;
};
const rename = (origin, newNames) => {
  const isObj = isObject(origin);
  if (isObj) {
    origin = [origin];
  }
  origin = parse(stringify(origin));
  const updateProperty = function(val, key, obj) {
    delete obj[key];
    key = newNames[key];
    obj[key] = val;
  };
  forEach(origin, (item) => {
    forEach(item, (val, key, obj) => {
      const isRename = (key in newNames);
      (isRename ? updateProperty : noop)(val, key, obj);
    });
  });
  return isObject ? origin[0] : origin;
};
const extend = (destination, sources) => {
  for (const key in sources) {
    const value = sources[key];
    if (!isUndefined(value)) {
      destination[key] = value;
    }
  }
  return destination;
};
const Defer = Promise;
const deferred = (callback) => {
  return new Defer(callback);
};
const tplEngine = (tpl, data, regexp) => {
  if (!(isArray(data))) {
    data = [data];
  }
  const ret = [];
  const replaceAction = (object) => {
    return tpl.replace(regexp || (/\\?\{([^}]+)\}/g), (match, name) => {
      if (match.charAt(0) === '\\') return match.slice(1);
      return (object[name] !== undefined) ? object[name] : '{' + name + '}';
    });
  };
  for (let i = 0, j = data.length; i < j; i++) {
    ret.push(replaceAction(data[i]));
  }
  return ret.join('');
};
// 暂时支持 String
const isContain = (str, keyword) => {
  return str.indexOf(keyword) > -1;
};
const isEqual = (source, target) => {
  return source === target;
};
const Cache = (cache) => {
  if (!isObject(cache)) {
    cache = {};
  }
  const set = (key, value) => {
    cache[key] = value;
  };
  const get = (key) => {
    return cache[key];
  };
  const remove = (key) => {
    delete cache[key];
  };
  const getKeys = () => {
    const keys = [];
    for (const key in cache) {
      keys.push(key);
    }
    return keys;
  };
  const clear = () => {
    cache = {};
  };
  return {
    set,
    get,
    remove,
    getKeys,
    clear
  };
};
const request = (url, option) => {
  return deferred((resolve, reject) => {
    option = option || {};
    const xhr = new XMLHttpRequest();
    const method = option.method || 'GET';
    xhr.open(method, url, true);
    const headers = option.headers || {};
    forEach(headers, (header, name) => {
      xhr.setRequestHeader(name, header);
    });
    const body = option.body || {};
    const isSuccess = () => {
      return /^(200|202)$/.test(xhr.status);
    };
    const timeout = option.timeout;
    if (timeout) {
      xhr.timeout = timeout;
    }
    xhr.onreadystatechange = function() {
      if (isEqual(xhr.readyState, 4)) {
        let { responseText } = xhr;
        responseText = responseText || '{}';
        const result = JSON.parse(responseText);
        if (isSuccess()) {
          resolve(result);
        } else {
          const { status } = xhr;
          extend(result, {
            status
          });
          reject(result);
        }
      }
    };
    xhr.onerror = (error) => {
      reject(error);
    };
    xhr.send(body);
  });
};
const map = (arrs, callback) => {
  return arrs.map(callback);
};
const filter = (arrs, callback) => {
  return arrs.filter(callback);
};
const uniq = (arrs, callback) => {
  const newData = []; const tempData = {};
  arrs.forEach(target => {
    const temp = callback(target);
    tempData[temp.key] = temp.value;
  });
  forEach(tempData, (val) => {
    newData.push(val);
  });
  return newData;
};
const some = (arrs, callback) => {
  return arrs.some(callback);
};
const toJSON = (value) => {
  return JSON.stringify(value);
};
const toArray = (obj) => {
  const arrs = [];
  forEach(obj, (v, k) => {
    arrs.push([k, v]);
  });
  return arrs;
};
const isPromise = (val) => {
  let isTrue = false;
  try {
    isTrue = Object.prototype.toString.call(val) === '[object Promise]' ||
      (val && val.then && val.catch && val.finally);
  } catch (e) {
    isTrue = false;
  }
  return isTrue;
};
function Timer(_option) {
  _option = _option || {};
  const option = {
    timeout: 0,
    // interval | timeout
    type: 'interval'
  };
  extend(option, _option);
  const timers = [];
  const { timeout, type } = option;
  const timerType = {
    resume: {
      interval: (callback, immediate) => {
        if (immediate) {
          callback();
        }
        return setInterval(callback, timeout);
      },
      timeout: (callback, immediate) => {
        if (immediate) {
          callback();
        }
        return setTimeout(callback, timeout);
      }
    },
    pause: {
      interval: (timer) => {
        return clearInterval(timer);
      },
      timeout: (timer) => {
        return clearTimeout(timer);
      }
    }
  };
  this.resume = function(callback, immediate) {
    callback = callback || noop;
    const { resume } = timerType;
    const timer = resume[type](callback, immediate);
    timers.push(timer);
  };
  this.pause = function() {
    const { pause } = timerType;
    forEach(timers, timer => {
      pause[type](timer);
    });
  };
}
const isInclude = (str, match) => {
  return str.indexOf(match) > -1;
};
const clone = (source) => {
  return JSON.parse(JSON.stringify(source));
};
function Index() {
  let index = 0;
  this.add = () => {
    index += 1;
  };
  this.get = () => {
    return index;
  };
  this.reset = () => {
    index = 0;
  };
}
function Observer() {
  let observers = [];
  this.add = (observer, force) => {
    if (isFunction(observer)) {
      if (force) {
        // eslint-disable-next-line no-return-assign
        return observers = [observer];
      }
      observers.push(observer);
    }
  };
  this.remove = (observer) => {
    observers = filter(observers, (_observer) => {
      return _observer !== observer;
    });
  };
  this.emit = (data) => {
    forEach(observers, (observer) => {
      observer(data);
    });
  };
}
function Prosumer() {
  const data = []; let isConsuming = false;
  this.produce = (res) => {
    data.push(res);
  };
  this.consume = (callback, finished) => {
    if (isConsuming) {
      return;
    }
    isConsuming = true;
    const next = () => {
      const res = data.shift();
      if (isUndefined(res)) {
        isConsuming = false;
        finished && finished();
        return;
      }
      callback(res, next);
    };
    next();
  };
  this.isExeuting = function() {
    return isConsuming;
  };
}
/*
 prosumer.consume(function(data, next){
  //dosomething
  next();
 });
*/
const Log = console;
const getBrowser = () => {
  const userAgent = navigator.userAgent;
  let name = ''; let version = '';
  if (/(Msie|Firefox|Opera|Chrome|Netscape)\D+(\d[\d.]*)/.test(userAgent)) {
    name = RegExp.$1;
    version = RegExp.$2;
  }
  if (/Version\D+(\d[\d.]*).*Safari/.test(userAgent)) {
    name = 'Safari';
    version = RegExp.$1;
  }
  return {
    name,
    version
  };
};

const string10to64 = (number) => {
  const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ+/'.split('');
  const radix = chars.length + 1;
  let qutient = +number;
  const arr = [];
  do {
    const mod = qutient % radix;
    qutient = (qutient - mod) / radix;
    arr.unshift(chars[mod]);
  } while (qutient);
  return arr.join('');
};

const getUUID = () => {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
};

/* 获取 22 位的 UUID */
const getUUID22 = () => {
  let uuid = getUUID();
  uuid = uuid.replace(/-/g, '') + '0';
  uuid = parseInt(uuid, 16);
  uuid = string10to64(uuid);
  if (uuid.length > 22) {
    uuid = uuid.slice(0, 22);
  }
  return uuid;
};

const Storage = (function() {
  const keyNS = 'luo-mu-';

  function isKeyExist(key) {
    // do not depend on value cause of '和0
    return Object.prototype.hasOwnProperty.call(localStorage, key) ||
      Object.prototype.hasOwnProperty.call(sessionStorage, key);
  }

  function get(key) {
    const tempKey = keyNS + key;
    if (!isKeyExist(tempKey)) {
      return null;
    }
    let val = localStorage.getItem(tempKey) || sessionStorage.getItem(tempKey);
    val = JSON.parse(val);
    if (val !== null &&
      Object.prototype.hasOwnProperty.call(val, 'type') &&
      Object.prototype.hasOwnProperty.call(val, 'data')) {
      return val.data;
    }
    return null;
  }

  function set(key, val, isTemp) {
    let store;
    if (isTemp) {
      store = sessionStorage;
    } else {
      store = localStorage;
    }
    store.setItem(keyNS + key, JSON.stringify({
      data: val,
      type: (typeof val)
    }));
  }

  function remove(key) {
    const tempKey = keyNS + key;
    localStorage.removeItem(tempKey);
    sessionStorage.removeItem(tempKey);
  }

  return {
    get: get,
    set: set,
    remove: remove
  };
})();

const handleObjKeys = (obj, event) => {
  obj = obj || {};
  const newObj = {};
  forEach(obj, (value, key) => {
    const newKey = event ? event(key) : key;
    newObj[newKey] = isObject(value) ? handleObjKeys(value, event) : value;
  });
  return newObj;
};

const clearEselessFields = (obj) => {
  obj = obj || {};
  const newObj = clone(obj);
  forEach(newObj, (value, key) => {
    if (isUndefined(value)) {
      delete newObj[key];
    } else if (isObject(value)) {
      value = clearEselessFields(value);
    }
  });
  return newObj;
};

const lineToHump = (str) => {
  return str.replace(/_(\w)/g, function(all, letter) {
    return letter.toUpperCase();
  });
};

const humpToLine = (str) => {
  return str.replace(/([A-Z])/g, '_$1').toLowerCase();
};

const getKeys = (_object) => {
  const keys = [];
  for (const key in _object) {
    keys.push(key);
  }
  return keys;
};

/* 去重合并 */
const merge = (arr1, arr2, options) => {
  options = options || {};
  const isReverse = options.isReverse;
  const newArr = [];
  forEach(arr1, (item) => {
    newArr.push(item);
  });
  forEach(arr2, (item) => {
    const index = newArr.indexOf(item);
    if (index > -1) {
      newArr.splice(index, 1);
    }
    isReverse ? newArr.unshift(item) : newArr.push(item);
  }, options);
  return newArr;
};

/**
 * 断言测试，当表达式值为 false，打印 Assertion failed 信息，同时返回表达式对应的 boolean 值
 * @param {Boolean} condition
 * @param {String} message
 */
const assert = (condition, message, ...args) => {
  /* eslint-disable no-console */
  if (console.assert) {
    console.assert(condition, message, ...args);
  }
  /* eslint-enable no-console */
  return !!condition;
};
const mergeObjs = (def, obj) => {
  if (!obj) {
    return def;
  } else if (!def) {
    return obj;
  }

  for (var i in obj) {
    // if its an object
    // eslint-disable-next-line eqeqeq
    if (obj[i] != null && obj[i].constructor == Object) {
      def[i] = mergeObjs(def[i], obj[i]);
    } else if (obj[i] != null && (obj[i] instanceof Array) && obj[i].length > 0) {
      // test to see if the first element is an object or not so we know the type of array we're dealing with.
      // eslint-disable-next-line eqeqeq
      if (obj[i][0].constructor == Object) {
        var newobjs = [];
        // create an index of all the existing object IDs for quick access.  There is no way to know how many items will be in the arrays.
        var objids = {};
        for (var x = 0, l = def[i].length; x < l; x++) {
          objids[def[i][x].id] = x;
        }

        // now walk through the objects in the new array
        // if the ID exists, then merge the objects.
        // if the ID does not exist, push to the end of the def array
        for (var x = 0, l = obj[i].length; x < l; x++) {
          var newobj = obj[i][x];
          if (objids[newobj.id] !== undefined) {
            def[i][x] = mergeObjs(def[i][x], newobj);
          } else {
            newobjs.push(newobj);
          }
        }

        for (var x = 0, l = newobjs.length; x < l; x++) {
          def[i].push(newobjs[x]);
        }
      } else {
        // eslint-disable-next-line no-redeclare
        for (var x = 0; x < obj[i].length; x++) {
          var idxObj = obj[i][x];
          if (def[i].indexOf(idxObj) === -1) {
            def[i].push(idxObj);
          }
        }
      }
    } else {
      def[i] = obj[i];
    }
  }
  return def;
};

const isRouterPath = (url) => {
  const rg = RegExp(/^\/[a-zA-Z]/);
  return rg.test(url);
};
const reuseRg = /^reuse:+/;
const isReuseCode = (code) => {
  const rg = RegExp(reuseRg);
  return rg.test(code);
};
function getOriginalCode(code) {
  return code.replace(new RegExp(reuseRg, 'gm'), '');
}
const optimizePath = (str) => {
  return str.replace(new RegExp(/[\/]+/, 'gm'), '/');
};
const changeCode = (str) => {
  return str.replace(new RegExp(/[\:]+/, 'gm'), '_');
};

const objToUrl = (obj) => {
  const arr = [];
  for (const i in obj) {
    if (obj.hasOwnProperty(i)) {
      if (isObject(obj[i])) {
        arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(JSON.stringify(obj[i])));
      } else if (isArray(obj[i])) {
        obj[i].map(item => {
          if (isDate(item)) {
            arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(item.Format('yyyy-MM-dd hh:mm:ss')));
          } else {
            arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(item));
          }
        });
      } else if (obj[i] !== '' && obj[i] !== null && obj[i] !== undefined) {
        arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
      }
    }
  }
  return arr.join('&');
};
export default {
  Prosumer,
  Log,
  Observer,
  Timer,
  isRouterPath,
  isReuseCode,
  getOriginalCode,
  optimizePath,
  isUndefined,
  changeCode,
  isBoolean,
  isString,
  isObject,
  isArray,
  isFunction,
  stringify,
  parse,
  rename,
  extend,
  mergeObjs,
  clone,
  deferred,
  Defer,
  forEach,
  tplEngine,
  isContain,
  noop,
  Cache,
  request,
  map,
  filter,
  uniq,
  some,
  isEqual,
  isEmpty,
  toJSON,
  isInclude,
  isNull,
  isNumber,
  toArray,
  isPromise,
  Index,
  getBrowser,
  getUUID,
  getUUID22,
  Storage,
  handleObjKeys,
  lineToHump,
  humpToLine,
  clearEselessFields,
  getKeys,
  merge,
  assert,
  objToUrl
};
